#include "Engine/StdAfx.h"
#include <Engine/Renderer/Camera.h>
#include <Engine/Math/Math.h>
#include <Engine/GlobalEnv.h>
#include <Engine/IEngine.h>
#include <Engine/IRenderer.h>
#include <Engine/IMouse.h>
#include <Engine/IKeyboard.h>
#include <Engine/ITimer.h>
#include <Engine/SceneGraph/BoundingVolume.h>

using namespace fastbird;

//----------------------------------------------------------------------------
Camera::Camera()
	: mViewPropertyChanged(true)
	, mProjPropertyChanged(true)
	, mUseCustomViewMat(0)
	, mFixedUp(false)
	, mSpeed(3.0f)
	, mMouseSensitive(0.005f)
{
	// view properties
	mDir = Vec3(0, 1, -1);
	mDir.Normalize();
	mPos = Vec3(0, 0, 50);
	mUp = Vec3(0, 0, 1);
	mRight = Vec3(1, 0, 0);
		
	// proj properties
	mFov = Radian(70);
	mWidth = (float)gEnv->pEngine->GetRenderer()->GetWidth();
	mHeight = (float)gEnv->pEngine->GetRenderer()->GetHeight();
	mNear = 0.1f;
	mFar = 4000.0f;
	gEnv->pEngine->AddInputListener(this, IInputListener::INPUT_LISTEN_PRIORITY_CAMERA, 0);
}

//----------------------------------------------------------------------------
Camera::~Camera()
{
}

//----------------------------------------------------------------------------
void Camera::SetNearFar(float n, float f)
{
	mNear = n;
	mFar = f;
	mProjPropertyChanged = true;
}

//----------------------------------------------------------------------------
void Camera::GetNearFar(float& n, float& f) const
{
	n = mNear;
	f = mFar;
}

//----------------------------------------------------------------------------
void Camera::Update()
{
	// world coordinates (Blender style)
	// x: right
	// y: forward
	// z: up
	if (mViewPropertyChanged)
	{
		Vec3 x, y, z;
		if (mFixedUp)
		{
			Vec3 up, right;
			if (mDir.Dot(Vec3::UNIT_Z) > 0.9f)
			{
				x = Vec3::UNIT_Y.Cross(mDir);
				z = x.Cross(mDir);
			}
			else if (mDir.Dot(Vec3::UNIT_Z) < - 0.9f)
			{
				x = mDir.Cross(Vec3::UNIT_Y);
				z = x.Cross(mDir);
			}
			else
			{
				x = mDir.Cross(Vec3::UNIT_Z);
				z = x.Cross(mDir);
			}
		}
		else
		{
			Vec3 right = mDir.Cross(Vec3::UNIT_Z);
			Vec3 up = right.Cross(mDir);
			x = right;
			y = mDir;
			z = up;
		}
			
		mUp = z;
		mRight = x;

		mDir.Normalize();
		mRight.Normalize();;
		mUp.Normalize();
		mViewMat = fastbird::MakeViewMatrix(mPos, mRight, mDir, mUp);
	}

	if (mProjPropertyChanged)
	{
		mProjMat = MakeProjectionMatrix(mFov, mWidth/mHeight, mNear, mFar);
			
		Mat44 swapMat(
			1, 0, 0, 0,
			0, 0, 1, 0,
			0, 1, 0, 0,
			0, 0, 0, 1); // change y and z axis

		mProjMat *= swapMat;			
	}

	if (mProjPropertyChanged || mViewPropertyChanged || mUseCustomViewMat==2)
	{
		if (mUseCustomViewMat)
		{
			mViewProjMat = mProjMat * mCustomViewMat;
			mUseCustomViewMat = 1;
		}
		else
		{
			mViewProjMat = mProjMat * mViewMat;
		}
		UpdateFrustum();

		mViewPropertyChanged = false;
		mProjPropertyChanged = false;
	}
}

//----------------------------------------------------------------------------
void Camera::UpdateFrustum()
{
	mPlanes[FRUSTUM_PLANE_LEFT].mNormal.x = mViewProjMat[3][0] + mViewProjMat[0][0];
	mPlanes[FRUSTUM_PLANE_LEFT].mNormal.y = mViewProjMat[3][1] + mViewProjMat[0][1];
	mPlanes[FRUSTUM_PLANE_LEFT].mNormal.z = mViewProjMat[3][2] + mViewProjMat[0][2];
	mPlanes[FRUSTUM_PLANE_LEFT].mConstant = -(mViewProjMat[3][3] + mViewProjMat[0][3]);

	mPlanes[FRUSTUM_PLANE_RIGHT].mNormal.x = mViewProjMat[3][0] - mViewProjMat[0][0];
	mPlanes[FRUSTUM_PLANE_RIGHT].mNormal.y = mViewProjMat[3][1] - mViewProjMat[0][1];
	mPlanes[FRUSTUM_PLANE_RIGHT].mNormal.z = mViewProjMat[3][2] - mViewProjMat[0][2];
	mPlanes[FRUSTUM_PLANE_RIGHT].mConstant = -(mViewProjMat[3][3] - mViewProjMat[0][3]);

	mPlanes[FRUSTUM_PLANE_TOP].mNormal.x = mViewProjMat[3][0] - mViewProjMat[1][0];
	mPlanes[FRUSTUM_PLANE_TOP].mNormal.y = mViewProjMat[3][1] - mViewProjMat[1][1];
	mPlanes[FRUSTUM_PLANE_TOP].mNormal.z = mViewProjMat[3][2] - mViewProjMat[1][2];
	mPlanes[FRUSTUM_PLANE_TOP].mConstant = -(mViewProjMat[3][3] - mViewProjMat[1][3]);

	mPlanes[FRUSTUM_PLANE_BOTTOM].mNormal.x = mViewProjMat[3][0] + mViewProjMat[1][0];
	mPlanes[FRUSTUM_PLANE_BOTTOM].mNormal.y = mViewProjMat[3][1] + mViewProjMat[1][1];
	mPlanes[FRUSTUM_PLANE_BOTTOM].mNormal.z = mViewProjMat[3][2] + mViewProjMat[1][2];
	mPlanes[FRUSTUM_PLANE_BOTTOM].mConstant = -(mViewProjMat[3][3] + mViewProjMat[1][3]);

	mPlanes[FRUSTUM_PLANE_NEAR].mNormal.x = mViewProjMat[3][0] + mViewProjMat[2][0];
	mPlanes[FRUSTUM_PLANE_NEAR].mNormal.y = mViewProjMat[3][1] + mViewProjMat[2][1];
	mPlanes[FRUSTUM_PLANE_NEAR].mNormal.z = mViewProjMat[3][2] + mViewProjMat[2][2];
	mPlanes[FRUSTUM_PLANE_NEAR].mConstant = -(mViewProjMat[3][3] + mViewProjMat[2][3]);

	mPlanes[FRUSTUM_PLANE_FAR].mNormal.x = mViewProjMat[3][0] - mViewProjMat[2][0];
	mPlanes[FRUSTUM_PLANE_FAR].mNormal.y = mViewProjMat[3][1] - mViewProjMat[2][1];
	mPlanes[FRUSTUM_PLANE_FAR].mNormal.z = mViewProjMat[3][2] - mViewProjMat[2][2];
	mPlanes[FRUSTUM_PLANE_FAR].mConstant = -(mViewProjMat[3][3] - mViewProjMat[2][3]);

	// Renormalise any normals which were not unit length
	for(int i=0; i<6; i++ ) 
	{
		float length = mPlanes[i].mNormal.Normalize();
		mPlanes[i].mConstant /= length;
	}
}

//----------------------------------------------------------------------------
const Mat44& Camera::GetViewMat()
{
	Update();
	if (mUseCustomViewMat)
		return mCustomViewMat;
	else
		return mViewMat;
}

//----------------------------------------------------------------------------
const Mat44& Camera::GetViewProjMat()
{
	Update();
	return mViewProjMat;
}

//----------------------------------------------------------------------------
void Camera::OnInput(IMouse* pMouse, IKeyboard* pKeyboard)
{
	if (pMouse && pMouse->IsValid() && !pKeyboard->IsKeyDown(VK_CONTROL))
	{
		long dx, dy;
		pMouse->GetDeltaXY(dx, dy);
		if (dx!=0)
		{
			Mat33 rot;
			rot.FromAxisAngle(mUp, -dx * mMouseSensitive);
			mDir = rot * mDir;
			mViewPropertyChanged = true;
		}

		if (dy!=0)
		{
			Mat33 rot;
			rot.FromAxisAngle(mRight, -dy * mMouseSensitive);
			Vec3 newDir = rot * mDir;
			if ( abs(newDir.Dot(Vec3::UNIT_Z)) < 0.999f )
			{
				mDir = newDir;
				mViewPropertyChanged = true;	
			}
		}
	}

	if (pKeyboard && pKeyboard->IsValid())
	{
		float dt = gEnv->pTimer->GetDeltaTime();
		float speed = mSpeed;
		if ( pKeyboard->IsKeyDown(VK_LSHIFT) )
		{
			speed*=10.0f;
		}
		bool moveForward = pKeyboard->IsKeyDown('W') && !pKeyboard->IsKeyDown(VK_CONTROL);
		bool moveBackward = pKeyboard->IsKeyDown('S');
		bool moveRight = pKeyboard->IsKeyDown('D');
		bool moveLeft = pKeyboard->IsKeyDown('A');
		bool moveDigonal = (moveForward || moveBackward) && (moveRight || moveLeft);
		if (moveDigonal)
		{
			//1.4142135 == sqrt(2)
			speed = speed / 1.4142135f;
		}

		if (moveForward)
		{
			mPos += mDir * speed * dt;
			mViewPropertyChanged = true;
		}
		if (moveBackward)
		{
			mPos -= mDir * speed * dt;
			mViewPropertyChanged = true;
		}

		if (moveRight)
		{
			mPos += mRight * speed * dt;
			mViewPropertyChanged = true;
		}
		if (moveLeft)
		{
			mPos -= mRight * speed * dt;
			mViewPropertyChanged = true;
		}
	}
}

//----------------------------------------------------------------------------
bool Camera::IsCulled(BoundingVolume* pBV) const
{
	for (int i=0; i<6; i++)
	{
		if (pBV->WhichSide(mPlanes[i])<0)
			return true;
	}

	return false;
}

	